package org.dreamwork.jasmine2.restful.client.gui.ui;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.log4j.Logger;
import org.dreamwork.gson.GsonHelper;
import org.dreamwork.jasmine2.restful.APIDefinition;
import org.dreamwork.jasmine2.restful.MethodDefinition;
import org.dreamwork.jasmine2.restful.client.gui.model.APITreeModel;
import org.dreamwork.jasmine2.restful.client.gui.model.ApiTreeNode;
import org.dreamwork.jasmine2.restful.client.gui.model.MethodTreeNode;
import org.dreamwork.jasmine2.restful.client.gui.model.RootTreeNode;
import org.dreamwork.jasmine2.restful.client.gui.util.Filters;
import org.dreamwork.jasmine2.restful.client.gui.util.HttpUtil;
import org.dreamwork.jasmine2.restful.client.gui.util.IMessageHandler;
import org.dreamwork.jasmine2.restful.client.gui.util.KeyUtil;
import org.dreamwork.secure.SecureContext;
import org.dreamwork.util.StringUtil;

import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.*;
import java.net.URL;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: seth.yang
 * Date: 2014/12/7
 * Time: 3:26
 */
public class MainFrame extends JFrame implements IMessageHandler {
    private JComboBox txtURL;
    private JPanel root;
    private JTree tree;
    private JTextField txtPublicKey;
    private JButton browseButton;
    private MappedTabPane tabs;
    private JSplitPane jsp;
    private JComboBox txtAppId;

    private APITreeModel model;
    private File file = new File ("../conf/list.cfg");
    private JFileChooser chooser;
    private JLabel status;
    private LogPane logPane = new LogPane ();

    private Logger logger;
    private APIDefinition api;

    public MainFrame () {
        super ("GUI Client for Jasmine Restful API");

        guiSetup ();
    }

    private void guiSetup () {
        // setup frame
        Toolkit kit = Toolkit.getDefaultToolkit ();
        Dimension d = kit.getScreenSize ();
        Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(getGraphicsConfiguration());
        setSize (d.width, d.height - insets.bottom - insets.top);
        setDefaultCloseOperation (WindowConstants.EXIT_ON_CLOSE);

        JPanel panel = new JPanel (new BorderLayout ());
        setContentPane (panel);

        JSplitPane p = new JSplitPane (JSplitPane.VERTICAL_SPLIT);
        p.setBorder (BorderFactory.createEmptyBorder (5, 5, 0, 5));
        p.setTopComponent (root);
        p.setBottomComponent (new JScrollPane (logPane));
        p.setOneTouchExpandable (true);
        p.setDividerLocation (d.height - 240);
        p.setDividerSize (10);
        panel.add ("Center", p);

        UIGlobal.registerMessageHandler (this);
        System.setOut (logPane.getOut ());
        System.setErr (logPane.getErr ());
        logger = Logger.getLogger (MainFrame.class);

        setStatusBar ();

        // setup txtURL
        txtURL.addActionListener (new ActionListener () {
            @Override
            public void actionPerformed (ActionEvent e) {
                try {
                    fetchAPI ();
                } catch (IOException e1) {
                    e1.printStackTrace ();
                }
            }
        });

        // setup tree
        tree.setRowHeight (25);
        tree.setModel (model = new APITreeModel ());
        tree.setCellRenderer (new TreeNodeUI ());
        tree.addMouseListener (new MouseInputAdapter () {
            @Override
            public void mouseClicked (MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton (e) && e.getClickCount () == 2) {
                    onTreeNodeDoubleClicked ();
                }
            }
        });

        // setup file chooser
        chooser = new JFileChooser ();
        chooser.addChoosableFileFilter (Filters.PEM);
        chooser.addChoosableFileFilter (Filters.X509);
        chooser.addChoosableFileFilter (Filters.CERTIFICATE);

        txtAppId.addActionListener (new ActionListener () {
            @Override
            public void actionPerformed (ActionEvent e) {
                String appId = (String) txtAppId.getSelectedItem ();
                if (!StringUtil.isEmpty (appId)) {
                    System.setProperty ("app-id", appId);

                    boolean found = false;
                    for (int i = 0; i < txtAppId.getItemCount (); i++) {
                        if (appId.equals (txtAppId.getItemAt (i))) {
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                        txtAppId.addItem (appId);

                    new Saver ().start ();
                }
            }
        });

        browseButton.addActionListener (new ActionListener () {
            @Override
            public void actionPerformed (ActionEvent e) {
                selectFile ();
            }
        });

        // other setup
        jsp.setDividerLocation (240);
        tabs.removeAll ();

        // setup public key
        txtPublicKey.addActionListener (new ActionListener () {
            @Override
            public void actionPerformed (ActionEvent e) {
                new KeyLoader ().start ();
            }
        });

        new Thread () {
            @Override
            public void run () {
                try {
                    load ();
                } catch (IOException ex) {
                    //
                }
            }
        }.start ();
    }

    private void fetchAPI () throws IOException {
        if (logger.isDebugEnabled ())
            logger.debug ("trying to fetch api from: " + txtURL.getSelectedItem ());
        String url = (String) txtURL.getSelectedItem ();
        if (!StringUtil.isEmpty (url)) {
            new Thread () {
                @Override
                public void run () {
                    String uri = (String) txtURL.getSelectedItem ();
                    try {
                        URL url = new URL (uri);
                        if ("http".equals (url.getProtocol ())) {
                            if (uri.endsWith ("?html")) uri = uri.substring (0, uri.length () - 5);
                            if (!uri.endsWith ("?json"))
                                uri += "?json";

                            String content = HttpUtil.get (uri);
                            Gson g = GsonHelper.getGson (true);
                            Map<String, Object> map = g.fromJson (content, new TypeToken<Map<String, Object>> () {
                            }.getType ());
                            model.setMap (map);

                            StringBuilder builder = new StringBuilder (url.getProtocol ()).append ("://");
                            builder.append (url.getHost ());
                            if (url.getPort () > 0)
                                builder.append (':').append (url.getPort ());
                            builder.append (url.getPath ());

                            String prefix = builder.toString ();
                            System.setProperty ("api-url-prefix", prefix);
                            boolean found = false;
                            for (int i = 0; i < txtURL.getItemCount (); i++) {
                                String text = (String) txtURL.getItemAt (i);
                                if (text.equals (prefix)) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found)
                                txtURL.addItem (prefix);
                            tabs.removeAll ();
                            save ();
                        } else {
                            showErrorMessage ("Can't fetch api info from url: " + url);
                        }
                    } catch (Exception ex) {
                        ex.printStackTrace ();
                        showErrorMessage ("Can't fetch api info from url: " + uri);
                    }
                }
            }.start ();
        }
    }

    private void onTreeNodeDoubleClicked () {
        TreePath path = tree.getSelectionPath ();
        if (path != null) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent ();
            String title = (String) node.getUserObject ();
            if (!tabs.exists (title)) {
                if (node instanceof RootTreeNode) {
                    RootTreeNode rtn = (RootTreeNode) node;
                    appendRestfulApiInfo (title, rtn);
                } else if (node instanceof MethodTreeNode) {
                    appendMethodPanel ((MethodTreeNode) node);
                }
            } else {
                for (int i = 0; i < tabs.getTabCount (); i++) {
                    String t = tabs.getTitleAt (i);
                    if (t.equals (title)) {
                        tabs.setSelectedIndex (i);
                        return;
                    }
                }
            }
        }
    }

    private void appendRestfulApiInfo (String title, RootTreeNode rtn) {
        APIDescPanel panel = new APIDescPanel (model.getDefs (), rtn.getSecureContext ());
        panel.setTitle (rtn.getTitle ());
        panel.setDescription (rtn.getDescription ());

        SecureContext context = rtn.getSecureContext ();
        if (context.getBlockEncryption () != null) {
            panel.setBlockEncryption (context.getBlockEncryption ().uri);
        }
        if (context.getKeyTransport () != null) {
            panel.setKeyWarp (context.getKeyTransport ().uri);
        }
        if (context.getSignature () != null)
            panel.setSignature (context.getSignature ().uri);

        tabs.addTab (title, panel.getPanel ());
    }

    private void appendMethodPanel (MethodTreeNode node) {
        ApiTreeNode p = (ApiTreeNode) node.getParent ();
        APIDefinition api = p.getApi ();
        MethodDefinition md = node.getMethodDefinition ();
        MethodPanel mp = new MethodPanel (api, md);
        mp.fill ();
        tabs.addTab (md.getTitle (), mp.getPanel ());
        tabs.setSelectedComponent (mp.getPanel ());
    }

    private void createUIComponents () {
        tabs = new MappedTabPane ();
    }

    private void showErrorMessage (String message) {
        JOptionPane.showMessageDialog (this, message, "Error", JOptionPane.ERROR_MESSAGE);
    }

    private void save () throws IOException {
        FileOutputStream fos = new FileOutputStream (file);
        PrintWriter writer = new PrintWriter (fos, true);
        try {
            writer.write ("[urls]\r\n");
            for (int i = 0; i < txtURL.getItemCount (); i++) {
                String text = (String) txtURL.getItemAt (i);
                if (!StringUtil.isEmpty (text)) {
                    writer.write (text.trim ());
                    writer.write ("\r\n");
                }
            }
            writer.write ("\r\n");
            writer.write ("[location]\r\n");
            writer.write (chooser.getCurrentDirectory ().getCanonicalPath ());
            writer.write ("\r\n");
            writer.write ("[appId]\r\n");
            for (int i = 0; i < txtAppId.getItemCount (); i ++) {
                String text = (String) txtAppId.getItemAt (i);
                if (!StringUtil.isEmpty (text)) {
                    writer.write (text.trim ());
                    writer.write ("\r\n");
                }
            }

            writer.flush ();
        } finally {
            fos.flush ();
            fos.close ();
        }
    }

    private void load () throws IOException {
        FileReader fr = new FileReader (file);
        try {
            BufferedReader reader = new BufferedReader (fr);
            String line;
            final SortedSet<String> set = new TreeSet<String> ();
            final Set<String> path = new HashSet<String> ();
            final Set<String> appId = new HashSet<String> ();
            Set<String> working = null;
            while ((line = reader.readLine ()) != null) {
                if (StringUtil.isEmpty (line)) continue;
                line = line.trim ();
                if ("[urls]".equals (line.trim ()))
                    working = set;
                else if ("[location]".equals (line.trim ()))
                    working = path;
                else if ("[appId]".equals (line))
                    working = appId;
                else if (working != null)
                    working.add (line.trim ());

                if (logger.isDebugEnabled ()) {
                    logger.debug ("load line: " + line);
                }
            }
            SwingUtilities.invokeLater (new Runnable () {
                @Override
                public void run () {
                    txtURL.addItem ("");
                    for (String url : set) {
                        txtURL.addItem (url);
                    }
                    if (!path.isEmpty ()) {
                        chooser.setCurrentDirectory (new File (path.iterator ().next ()));
                    }
                    txtAppId.addItem ("");
                    for (String app : appId)
                        txtAppId.addItem (app);
                }
            });
        } finally {
            fr.close ();
        }
    }

    private void selectFile () {
        chooser.showOpenDialog (this);
        File file = chooser.getSelectedFile ();
        if (file != null) {
            try {
                txtPublicKey.setText (file.getCanonicalPath ());
                System.setProperty ("public-key", file.getCanonicalPath ());
                new Saver ().start ();
                new KeyLoader ().start ();
            } catch (IOException e) {
                e.printStackTrace ();
            }
        }
    }

    private void setStatusBar () {
        JPanel bar = new JPanel ();
        bar.setBorder (BorderFactory.createLoweredBevelBorder ());
        bar.add (status = new JLabel ("Ready"));
        status.setHorizontalAlignment (SwingConstants.LEFT);
        getContentPane ().add ("South", bar);
    }

    @Override
    public void setStatus (String status) {
        this.status.setText (status);
    }

    @Override
    public void log (Object message) {
        logPane.log (message);
    }

    @Override
    public void warn (Object message) {
        logPane.warn (message);
    }

    @Override
    public void error (Object message) {
        logPane.error (message);
    }

    @Override
    public void error (Object o, Throwable t) {
        logPane.error (o, t);
    }

    private class Saver extends Thread {
        @Override
        public void run () {
            try {
                save ();
            } catch (IOException ex) {
                ex.printStackTrace ();
                showErrorMessage (ex.getMessage ());
            }
        }
    }

    private class KeyLoader extends Thread {
        @Override
        public void run () {
            String path = txtPublicKey.getText ();
            if (!StringUtil.isEmpty (path)) {
                File file = new File (path);
                if (file.exists ()) {
                    try {
                        KeyUtil.loadKey (file);
                    } catch (Exception ex) {
                        showErrorMessage ("Can't load public from " + path + ", cause: " + ex.getMessage ());
                    }
                }
            }
        }
    }
}